﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Fiddler;
using System.Collections;
using CSharp.Libs.Koc.KernelModels;
using CSharp.Libs.Misc;

namespace CSharp.Libs.Koc
{
    public class TrafficMonitor
    {
        #region declarations
        // Main kernel
        private Kernel _KRNL = null;

        // FiddlerCore specific
        //private Proxy fpSecureEndpoint;
        private List<Session> lstAllSessions = new List<Session>();
        //private String strSecureEndpointHostname = "localhost";
        private String strSAZInfo = "NoSAZ";
        private Boolean _fUseFiddlerLog = false;

        // receiving events
        private Boolean _fReceivingEvents = false;

        // MainSrc and Seed specific
        private Int32 _intSeedCount = 0;
        private String _strRecognizeStringMainSrcPhp = "";
        private String _strMainSrcSeedStart = "";
        private String _strMainSrcSeedEnd = "";
        private Dictionary<int, object> _dicSeeds = new Dictionary<int, object>();
        private const String KEY_SEED_EXTENSION_FULL_URL = "FullUrl";
        private const String KEY_SEED_EXTENSION_SEED_COUNTER = "SeedCounter";

        // Seed update specific
        private Hashtable _htUpdateSeed = null;

        // fb parameter specific
        private Dictionary<String, String> _dicFbPostParameters = new Dictionary<String, String>();
        private String _strRecognizeStringFbSig = "";
        private String _strCurrentServerId = "";

        // fb userid specific
        private const String FBUID_SEEK = "Env={user:";
        private String _strUserID = "";
        private Boolean _fUserIsValid = false;

        #region events
        public delegate void SeedReceivedEventHandler(object sender, SeedReceivedEventArgs srea);
        public event SeedReceivedEventHandler SeedReceived;
        private void ThrowSeedReceivedEvent(
            SeedReceivedEventArgs srea)
        {
            if (SeedReceived != null)
            {
                SeedReceived(this, srea);
            }
        }

        public delegate void FbParametersChangedEventHandler(object sender);
        public event FbParametersChangedEventHandler FbParametersChanged;
        private void ThrowFbParametersChangedEvent()
        {
            if (FbParametersChanged != null)
            {
                FbParametersChanged(this);
            }
        }

        public delegate void KocServerChangedEventHandler(object sender);
        public event KocServerChangedEventHandler KocServerChanged;
        private void ThrowKocServerChangedEvent()
        {
            if (KocServerChanged != null)
            {
                KocServerChanged(this);
            }
        }

        public delegate void UserIsValidChangedEventHandler(object sender);
        public event UserIsValidChangedEventHandler UserIsValidChanged;
        private void ThrowUserIsValidChangedEvent()
        {
            if (UserIsValidChanged != null)
            {
                UserIsValidChanged(this);
            }
        }

        public delegate void UserIdChangedEventHandler(object sender);
        public event UserIdChangedEventHandler UserIdChanged;
        private void ThrowUserIdChangedEvent()
        {
            if (UserIdChanged != null)
            {
                UserIdChanged(this);
            }
        }

        public delegate void UpdateSeedReceivedEventHandler(object sender, UpdateSeedReceivedEventArgs ea);
        public event UpdateSeedReceivedEventHandler UpdateSeedReceived;
        private void ThrowUpdateSeedReceivedEvent(
            Hashtable UpdateSeed)
        {
            if (UpdateSeedReceived != null)
            {
                UpdateSeedReceived(this, new UpdateSeedReceivedEventArgs(UpdateSeed));
            }
        }

        public delegate void ReceivingEventsChangedEventHandler(object sender);
        public event ReceivingEventsChangedEventHandler ReceivingEventsChanged;
        private void ThrowReceivingEventsChangedEvent()
        {
            if (ReceivingEventsChanged != null)
            {
                ReceivingEventsChanged(this);
            }
        }
        #endregion
        #endregion



        #region constructor / terminator
        public TrafficMonitor(
            Kernel KRNL)
        {
            _KRNL = KRNL;

            //Get options
            _strRecognizeStringMainSrcPhp = _KRNL.LAO.GetOption(constants.LAO_RECOGNIZE_STRING_PHP_MAIN_SRC);
            _strMainSrcSeedStart = _KRNL.LAO.GetOption(constants.LAO_SEEK_STRING_MAIN_SRC_SEED_START);
            _strMainSrcSeedEnd = _KRNL.LAO.GetOption(constants.LAO_SEEK_STRING_MAIN_SRC_SEED_END);
            _strRecognizeStringFbSig = _KRNL.LAO.GetOption(constants.LAO_RECOGNIZE_STRING_FB_SIG);

            //Prepare FiddlerCore
            _StartupFiddler();
        }

        ~TrafficMonitor()
        {
            //very important stop listening and remove proxy
            FiddlerApplication.Shutdown();
        }
        #endregion



        #region event handling
        /// <summary>FiddlerApplication_AfterSessionComplete</summary>
        /// <param name="oSession"></param>
        /// <remarks>executed after receiving a complete response</remarks>
        /// <history>25.07.2011 created</history>
        void FiddlerApplication_AfterSessionComplete(Session oSession)
        {
            try
            {
                if (_fUseFiddlerLog)
                {
                    FiddlerApplication.Log.LogString("FiddlerApplication_AfterSessionComplete: received URL: " + oSession.fullUrl);
                }

                //set ReceivingEvents switch
                _fReceivingEvents = true;
                ThrowReceivingEventsChangedEvent();

                //Check for UserID
                _CheckUserID(oSession);

                /************************************************
                 *                                              *
                 *      functions only for allowed users        *
                 *                                              *
                 ************************************************/
                if (_fUserIsValid)
                {
                    //Receive getChat.php as a basic starting point to get necessary data to send own commands
                    _ReceiveGetChat(oSession);

                    //Receive Seed
                    _ReceiveMainSeed(oSession);

                    //Reaction on UpdateSeed
                    _ReceivedSeedUpdate(oSession);
                }

                //Test UserValidator
                if (oSession.fullUrl.Contains("allowedusers.php"))
                {
                }
            }
            catch (Exception ex)
            {
                _KRNL.EC.AddException(ex, this.ToString(), _KRNL.EC.Tools.GetCurrentMethodName());
            }
        }

        /// <summary>Log_OnLogString</summary>
        /// <param name="sender"></param>
        /// <param name="e">LogString property contains log entry</param>
        /// <remarks>executed after a LogString function was called</remarks>
        /// <history>created  12.10.2011</history>
        void Log_OnLogString(object sender, LogEventArgs e)
        {
            throw new NotImplementedException();
        }
        #endregion

        
        
        #region private functions
        #region FiddlerCore initialize
        /// <summary>_StartupFiddler</summary>
        /// <history>created  12.10.2011</history>
        private void _StartupFiddler()
        {
            try
            {
                //subscribe event(s)
                FiddlerApplication.AfterSessionComplete += new SessionStateHandler(FiddlerApplication_AfterSessionComplete);
                if (_fUseFiddlerLog)
                {
                    FiddlerApplication.Log.OnLogString += new EventHandler<LogEventArgs>(Log_OnLogString);
                }

                if (_fUseFiddlerLog)
                {
                    FiddlerApplication.Log.LogString(String.Format("Starting {0} ({1})...", Fiddler.FiddlerApplication.GetVersionString(), strSAZInfo));
                }

                //Configure Fiddler
                /* FiddlerCoreStartupFlags.Default:
                 * - RegisterAsSystemProxy
                 * - DecryptSSL
                 * - AllowRemoteClients
                 * - ChainToUpstreamGateway
                 * - MonitorAllConnections
                 * - CaptureLocalhostTraffic
                 */
                CONFIG.IgnoreServerCertErrors = false;
                FiddlerApplication.Prefs.SetBoolPref("fiddler.network.streaming.abortifclientaborts", true);
                FiddlerCoreStartupFlags oFCSF;
                oFCSF = FiddlerCoreStartupFlags.Default & ~FiddlerCoreStartupFlags.DecryptSSL; //Use default, but ommit DecryptSSL, cause then hhtps Websites won't load for some reason (CA probs?)
                //oFCSF = FiddlerCoreStartupFlags.RegisterAsSystemProxy; //This as single Flag also works. It's the minimum.

                FiddlerApplication.Startup(8877, oFCSF); //Start listening

                if (_fUseFiddlerLog)
                {
                    FiddlerApplication.Log.LogString("Starting with settings: [" + oFCSF + "]");
                    FiddlerApplication.Log.LogString("Using Gateway: " + ((CONFIG.bForwardToGateway) ? "TRUE" : "FALSE"));
                }
            }
            catch (Exception ex)
            {
                _KRNL.EC.AddException(ex, this.ToString(), _KRNL.EC.Tools.GetCurrentMethodName());
            }
        }
        #endregion

        #region User ID check
        /// <summary>_CheckUserID</summary>
        /// <param name="oSession"></param>
        /// <history>created  11.10.2011</history>
        private void _CheckUserID(
            Session oSession)
        {
            //http://www.facebook.com/ajax/typeahead/search/first_degree.php?__a=1&filter[0]=app&filter[1]=page&filter[2]=group&filter[3]=friendlist&lazy=1&viewer=1353915001&token=v7&stale_ok=1&__user=1353915001
            //http://0.274.channel.facebook.com/x/95433351/684797477/false/p_1353915001=27818
            //getChat.php

            //Start getting the user id from different places
            String strNewUserID = "";

            //Check KoC for User ID
            if (oSession.fullUrl.Contains("kingdomsofcamelot.com"))
            {
                //Check getChat.php for User ID
                if (oSession.fullUrl.Contains("getChat.php"))
                {
                    //String strTest = oSession.GetResponseBodyAsString();
                    String strPostData = oSession.GetRequestBodyAsString();
                    Dictionary<String, String> dicNewParams = _KRNL.UH.GetParameterDictionary(strPostData);
                    strNewUserID = dicNewParams[URLHolder.PARAM_FB_SIG_USER];
                }

                String strResponse = oSession.GetResponseBodyAsString();
                if (strResponse.Contains(FBUID_SEEK))
                {
                    strNewUserID = _GetFbUserId(strResponse);
                }
            }
            
            //Check facebook data
            if (oSession.fullUrl.Contains("facebook.com") &&
                oSession.fullUrl.Contains("first_degree.php") &&
                oSession.fullUrl.Contains("__user="))
            {
                String strPostData = oSession.GetRequestBodyAsString();
                strPostData = oSession.fullUrl.Split('?')[1];
                Dictionary<String, String> dicNewParams = _KRNL.UH.GetParameterDictionary(strPostData);
                if (dicNewParams.ContainsKey("__user"))
                {
                    strNewUserID = dicNewParams["__user"];
                }
            }

            //Now check if the new user id is really new. :)
            //react only if user id changed
            if (strNewUserID != "" &&
                strNewUserID != _strUserID)
            {
                //Check in new user id and fire event
                _strUserID = strNewUserID;
                ThrowUserIdChangedEvent();

                Boolean fNewUserIsValid = _KRNL.UV.UserIsValid(_strUserID);
                if (_fUserIsValid != fNewUserIsValid)
                {
                    _fUserIsValid = fNewUserIsValid;
                    ThrowUserIsValidChangedEvent();
                }
            }
        }

        /// <summary>_GetFbUserId</summary>
        /// <param name="strSeek"></param>
        /// <returns></returns>
        /// <history>24.03.2011 created</history>
        /// <history>25.07.2011 adapted from old TrafficMonitor using HttpMonitor.dll COM class</history>
        private String _GetFbUserId(
            String strSeek)
        {
            try
            {
                //get starting index of uid
                Int32 intStart = strSeek.IndexOf(FBUID_SEEK) + FBUID_SEEK.Length;

                if (intStart > 0)
                {
                    //get ending index of seed variable in page's source
                    Int32 intEnd = strSeek.IndexOf(",", intStart);

                    if (intEnd > intStart)
                    {
                        //get seed variable's string
                        return strSeek.Substring(intStart, intEnd - intStart);
                    }
                }
            }
            catch (Exception ex)
            {
                _KRNL.EC.AddException(ex, this.ToString(), _KRNL.EC.Tools.GetCurrentMethodName());
            }

            return "";
        }
        #endregion

        #region getChat receiving
        /// <summary>_ReceiveGetChat</summary>
        /// <param name="oSession"></param>
        /// <history>created  13.10.2011</history>
        private void _ReceiveGetChat(
            Session oSession)
        {
            //receive KoC specific events
            if (oSession.fullUrl.Contains("getChat.php"))
            {
                //String strTest = oSession.GetResponseBodyAsString();
                String strPostData = oSession.GetRequestBodyAsString();

                if (!_KRNL.UH.URLGetChat.URLMaybeValid)
                {
                    _KRNL.UH.ParseURLGetChat(String.Concat(oSession.fullUrl, "?", strPostData));
                    _strCurrentServerId = _KRNL.UH.URLGetChat.Parameters[URLHolder.PARAM_SID];//25.03.2011 Added ServerId

                    ThrowFbParametersChangedEvent();
                    ThrowKocServerChangedEvent();
                }
                else
                {
                    //Check if FbParameters changed
                    Dictionary<String, String> dicNewParams = _KRNL.UH.GetParameterDictionary(strPostData);
                    Boolean fParamsInvalid = false;

                    //25.03.2011 Added ServerId
                    if (_strCurrentServerId != dicNewParams[URLHolder.PARAM_SID])
                    {
                        _strCurrentServerId = dicNewParams[URLHolder.PARAM_SID];
                        ThrowKocServerChangedEvent();
                    }

                    //Compare fb sig parameters
                    foreach (KeyValuePair<String, String> KVP in _KRNL.UH.URLGetChat.Parameters)
                    {
                        if (KVP.Key.Contains(_strRecognizeStringFbSig))
                        {
                            if (!dicNewParams.ContainsKey(KVP.Key))
                            {
                                fParamsInvalid = true;
                                break;
                            }
                            else
                            {
                                if (dicNewParams[KVP.Key] != KVP.Value)
                                {
                                    fParamsInvalid = true;
                                    break;
                                }
                            }
                        }
                    }

                    //if FbParameters changed, parse new.
                    if (fParamsInvalid)
                    {
                        _KRNL.UH.ParseURLGetChat(String.Concat(oSession.fullUrl, "?", strPostData));
                        ThrowFbParametersChangedEvent();
                    }
                }
            }
        }
        #endregion 

        #region seed receiving
        /// <summary>_GetSeed
        ///     retreives the seed variable off the Main_Src page.
        /// </summary>
        /// <history>21.03.2011 created</history>
        /// <history>25.07.2011 adapted from old TrafficMonitor using HttpMonitor.dll COM class</history>
        /// <history>14.10.2011 added handling of multiple servers</history>
        private void _ReceiveMainSeed(
            Session oSession)
        {
            try
            {
                if (oSession.fullUrl.ToLower().Contains(_strRecognizeStringMainSrcPhp))
                {
                    String strMainPage = oSession.GetResponseBodyAsString();
                    
                    //get starting index of seed variabe in page's source
                    Int32 intStart = strMainPage.IndexOf(_strMainSrcSeedStart) + _strMainSrcSeedStart.Length + 1;

                    if (intStart > 0)
                    {
                        //get ending index of seed variable in page's source
                        Int32 intEnd = strMainPage.IndexOf(_strMainSrcSeedEnd, intStart + 1);

                        if (intEnd > intStart)
                        {
                            //get seed variable's string
                            String strSeed = strMainPage.Substring(intStart, intEnd - intStart);

                            if (strSeed != "")
                            {
                                //get Hashtable of Seed
                                Hashtable htSeed = JSON.JsonDecode(strSeed) as Hashtable;
                                
                                if (htSeed != null)
                                {
                                    //get ServerId
                                    int intServerId = _GetServerIdOfSeed(oSession.fullUrl);

                                    //Add seed to dictionary
                                    if (_dicSeeds.ContainsKey(intServerId))
                                    {
                                        //full URL and counter refresh
                                        int intLastCount = (int)((Hashtable)_dicSeeds[intServerId])[KEY_SEED_EXTENSION_SEED_COUNTER];

                                        htSeed.Add(KEY_SEED_EXTENSION_FULL_URL, oSession.fullUrl);
                                        htSeed.Add(KEY_SEED_EXTENSION_SEED_COUNTER, intLastCount + 1);

                                        _dicSeeds[intServerId] = htSeed;
                                    }
                                    else
                                    {
                                        //expand Hashtable by full URL and counter
                                        htSeed.Add(KEY_SEED_EXTENSION_FULL_URL, oSession.fullUrl);
                                        htSeed.Add(KEY_SEED_EXTENSION_SEED_COUNTER, 1);

                                        _dicSeeds.Add(intServerId, htSeed);
                                    }

                                    //increment seed counter and throw seed event
                                    _intSeedCount++;
                                    ThrowSeedReceivedEvent(new SeedReceivedEventArgs(intServerId, htSeed));
                                }
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                _KRNL.EC.AddException(ex, this.ToString(), _KRNL.EC.Tools.GetCurrentMethodName());
            }
        }

        /// <summary>_GetServerIdOfSeed</summary>
        /// <param name="FullUrl"></param>
        /// <returns></returns>
        /// <history>created  14.10.2011</history>
        private int _GetServerIdOfSeed(
            String FullUrl)
        {
            int intServerId = 0;

            //get number of s parameter (preferred way)
            String[] strUrlSplit = FullUrl.Split('?');

            if (strUrlSplit.Count() == 2 &&
                strUrlSplit[1] != "")
            {
                Dictionary<String, String> dicNewParams = _KRNL.UH.GetParameterDictionary(strUrlSplit[1]);

                if (dicNewParams != null &&
                    dicNewParams.Count > 0)
                {
                    if (dicNewParams.ContainsKey("s"))
                    {
                        if (!int.TryParse(dicNewParams["s"], out intServerId))
                        {
                            intServerId = -1;
                        }
                    }
                }
            }

            //get number after www only if no number
            if (intServerId <= 0)
            {
                //not yet implemented
                throw new NotImplementedException();
            }

            return intServerId;
        }
        #endregion

        #region seed update receiving
        /// <summary>_ReceivedSeedUpdate</summary>
        /// <returns></returns>
        /// <history>10.05.2011 created</history>
        /// <history>25.07.2011 adapted from old TrafficMonitor using HttpMonitor.dll COM class</history>
        private void _ReceivedSeedUpdate(
            Session oSession)
        {
            try
            {
                if (oSession.fullUrl.Contains(URLHolder.PHP_UPDATE_SEED))
                {
                    String strUpdateSeedJson = oSession.GetResponseBodyAsString();
                    _htUpdateSeed = JSON.JsonDecode(strUpdateSeedJson) as Hashtable;

                    if (_KRNL.ReturnHashtableOk(_htUpdateSeed))
                    {
                        ThrowUpdateSeedReceivedEvent(_htUpdateSeed);
                    }
                }
            }
            catch (Exception ex)
            {
                _KRNL.EC.AddException(ex, this.ToString(), _KRNL.EC.Tools.GetCurrentMethodName());
            }
        }
        #endregion
        #endregion



        #region interface
        #region properties
        #region basic info and settings
        public Boolean ReceivingEvents
        {
            get
            {
                return _fReceivingEvents;
            }
        }

        public Boolean UseFiddlerLog
        {
            get
            {
                return _fUseFiddlerLog;
            }
            set
            {
                _fUseFiddlerLog = value;
            }
        }
        #endregion

        #region seed
        /// <summary>Seeds dictionary
        ///  - contains the current received seed of every server visited</summary>
        /// <remarks></remarks>
        /// <history>created  14.10.2011 new seed dictionary</history>
        public Dictionary<int, object> Seeds
        {
            get
            {
                return _dicSeeds;
            }
        }

        /// <summary>SeedCount</summary>
        /// <history>14.10.2011 obsolete. counts every seed even from different servers</history>
        public Int32 SeedCount
        {
            get
            {
                return _intSeedCount;
            }
        }

        public Hashtable UpdateSeed
        {
            get
            {
                return _htUpdateSeed;
            }
        }
        #endregion

        #region FB parameters
        public Dictionary<String, String> fbPostParameters
        {
            get
            {
                return _dicFbPostParameters;
            }
        }

        public String FBUserID
        {
            get
            {
                return _strUserID;
            }
        }

        public Boolean UserIsValid
        {
            get
            {
                return _fUserIsValid;
            }
        }

        public String CurrentServerId
        {
            get
            {
                return _strCurrentServerId;
            }
        }
        #endregion
        #endregion

        #region methods
        #endregion
        #endregion
    }
}
